package bd;

import java.sql.*;
import java.util.*;
import java.util.regex.Pattern;

public class GestorBD {

	private static final String TABLE_NAME = "records",
			HOST = "jdbc:mysql://localhost:3306/erasmus", USER = "root",
			PASSWORD = "root", DRIVER = "org.gjt.mm.mysql.Driver";

	private static Statement statement;

	private static Connection connection;

	private static ResultSet rs;

	private static PreparedStatement ps;

	/**
	 * CONSTRUCTOR
	 */
	public GestorBD() {
		super();
	}

	/**
	 * Realiza una conexion a la BD
	 * 
	 * @throws Exception
	 */
	public static void conectar() throws Exception {
		try {
			System.out.println("Conectando...");
			Class.forName(DRIVER).newInstance(); // cargar el driver
			// crear la conexion
			connection = DriverManager.getConnection(HOST, USER, PASSWORD);
			System.out.println("Conexion a la BD con exito!!!");
		} catch (Exception e) {
			throw new Exception("ERROR en conexion: " + e.toString());
		}
	}

	/**
	 * Se desconecta de la BD
	 * 
	 * @throws SQLException
	 */
	public static void desconectar() throws SQLException {
		try {
			if (rs != null)
				rs.close();
			if (connection != null) {
				connection.close();
			}
			System.out.println("Desconectado");
		} catch (SQLException sqle) {
			throw new SQLException("ERROR al desconectar: " + sqle.toString());
		}
	}

	/**
	 * Empieza la transaccion Se deshabilita autocommit (habilitado por
	 * defecto), para que las sentencias no hagan commit hasta que se indique
	 * (VER:
	 * http://java.sun.com/docs/books/tutorial/jdbc/basics/transactions.html)
	 * 
	 * @throws SQLException
	 */
	public static void comenzarTransaccion() throws SQLException {
		try {
			connection.setAutoCommit(false);
		} catch (SQLException e) {
			e.printStackTrace();
			throw new SQLException("ERROR al empezar la transaccion: "
					+ e.toString());
		}
	}

	/**
	 * Realiza el commit Se llama a commit() implicitamente y luego se vuelve a
	 * habilitar el autocommit (VER:
	 * http://java.sun.com/docs/books/tutorial/jdbc/basics/transactions.html)
	 * 
	 * @throws SQLException
	 */
	public static void hacerCommit() throws SQLException {
		try {
			connection.commit();
			connection.setAutoCommit(true);
		} catch (SQLException e) {
			e.printStackTrace();
			throw new SQLException("Error al hacer commit: " + e.toString());
		}
	}

	/**
	 * Realiza una consulta a la BD dada una consulta
	 * 
	 * @param query
	 * @return
	 */
	public static ResultSet consulta(StringBuffer query)
			throws ConsultaInadecuadaException, Exception {
		rs = null;
		try {
			statement = connection.createStatement();
			// statement =
			// connection.createStatement(ResultSet.CONCUR_UPDATABLE,
			// ResultSet.TYPE_SCROLL_INSENSITIVE);
			/*
			 * NOTA: Las consultas SELECT devuelven ResultSets pero otros como
			 * INSERT no devuelven ese tipo de datos por lo que en vez de usar
			 * executeQuery hay que usar execute que devuelve un booleano.
			 */
			rs = statement.executeQuery(query.toString());
		} catch (SQLException e) {
			System.err.println("ERROR en consulta: " + e.toString());
		}
		return rs;
	}

	/**
	 * Realiza una insercion recibiendo un array de String y la consulta de
	 * insercion
	 * 
	 * @param query
	 * @param values
	 * @return
	 * @throws Exception
	 */
	/*
	 * public static ResultSet inserta(String query, String[] values) throws
	 * NullPointerException, SQLException, IndexOutOfBoundsException, Exception {
	 * int id = -1; rs = null; ps = null; if (values == null || values.length ==
	 * 0) throw new NullPointerException("Los parametros son vacios"); try { ps =
	 * connection.prepareStatement(query); /* Esta es la nueva mejora:Ahora es
	 * posible aplicar INSERT's m�s gen�ricos. Se puede insertar Strings en los
	 * '?' de las consultas de inserci�n de tuplas aunque se asocien a valores
	 * que no sean de tipo cadena, como enteros, reales, etc.
	 */
	/*
	 * for (int i = 0; i < values.length; i++) { ps.setString(i + 1, values[i]); }
	 * ps.execute(); /* Con esta sentencia se consigue el ultimo ResultSet
	 * insertado. Util para inserciones en varias tablas donde existe un FK
	 * entre una tabla hija y otra padre
	 */
	/*
	 * rs = ps.executeQuery("SELECT LAST_INSERT_ID()");
	 *  } catch (SQLException sqle) { throw new SQLException("Fallo de
	 * insercion: " + sqle.toString() + "->" + quote(ps.toString())); } catch
	 * (IndexOutOfBoundsException ioobe) { throw new
	 * IndexOutOfBoundsException("Se salio de indice!"); } catch (Exception e) {
	 * throw new Exception( "Se produjo un error desconocido durante la
	 * operaci�n solicitada." + "Olvido realizar una conexion previa con la BD?
	 * ;-)" + connection.isClosed()); } return rs; }
	 */

	public static void insertaSimple (StringBuffer query, String[] values)
			throws NullPointerException, SQLException,
			IndexOutOfBoundsException, Exception {
		rs = null;
		ps = null;
		if (values == null || values.length == 0)
			throw new NullPointerException("Los parametros son vacios");
		try {
			ps = connection.prepareStatement(query.toString());
			/*
			 * Esta es la nueva mejora:Ahora es posible aplicar INSERT's m�s
			 * gen�ricos. Se puede insertar Strings en los '?' de las consultas
			 * de inserci�n de tuplas aunque se asocien a valores que no sean de
			 * tipo cadena, como enteros, reales, etc.
			 */
			for (int i = 0; i < values.length; i++) {
				ps.setString(i + 1, values[i]);
			}
			ps.execute();
			/*
			 * Con esta sentencia se consigue el ultimo ResultSet insertado.
			 * Util para inserciones en varias tablas donde existe un FK entre
			 * una tabla hija y otra padre
			 */
		//	rs = ps.executeQuery("SELECT LAST_INSERT_ID()");
			/*
			 * Ahora obtenemos el id del ResulSet insertado y lo devolvemos
			 */
		/*	if (!rs.next())
				throw new Exception(
						"No se puedo obtener el id de la tupla insertada");
			id = rs.getInt(1);*/
		} catch (SQLException sqle) {
			throw new SQLException("Fallo de insercion: " + sqle.toString()
					+ "->" + quote(ps.toString()));
		} catch (IndexOutOfBoundsException ioobe) {
			throw new IndexOutOfBoundsException("Se salio de indice!");
		} catch (Exception e) {
			throw new Exception(
					"Se produjo un error desconocido durante la operaci�n solicitada."
							+ "Olvido realizar una conexion previa con la BD? ;-)"
							+ connection.isClosed());
		}
	}

	public static int inserta(StringBuffer query, String[] values)
			throws NullPointerException, SQLException,
			IndexOutOfBoundsException, Exception {
		int id = -1;
		rs = null;
		ps = null;
		if (values == null || values.length == 0)
			throw new NullPointerException("Los parametros son vacios");
		try {
			ps = connection.prepareStatement(query.toString());
			/*
			 * Esta es la nueva mejora:Ahora es posible aplicar INSERT's m�s
			 * gen�ricos. Se puede insertar Strings en los '?' de las consultas
			 * de inserci�n de tuplas aunque se asocien a valores que no sean de
			 * tipo cadena, como enteros, reales, etc.
			 */
			for (int i = 0; i < values.length; i++) {
				ps.setString(i + 1, values[i]);
			}
			ps.execute();
			/*
			 * Con esta sentencia se consigue el ultimo ResultSet insertado.
			 * Util para inserciones en varias tablas donde existe un FK entre
			 * una tabla hija y otra padre
			 */
			rs = ps.executeQuery("SELECT LAST_INSERT_ID()");
			/*
			 * Ahora obtenemos el id del ResulSet insertado y lo devolvemos
			 */
			if (!rs.next())
				throw new Exception(
						"No se puedo obtener el id de la tupla insertada");
			//
			id = rs.getInt(1);
		} catch (SQLException sqle) {
			throw new SQLException("Fallo de insercion: " + sqle.toString()
					+ "->" + quote(ps.toString()));
		} catch (IndexOutOfBoundsException ioobe) {
			throw new IndexOutOfBoundsException("Se salio de indice!");
		} catch (Exception e) {
			throw new Exception(
					"Se produjo un error desconocido durante la operaci�n solicitada."
							+ "Olvido realizar una conexion previa con la BD? ;-)"
							+ connection.isClosed());
		}
		return id;
	}

	/**
	 * Insercion pero adaptada a un solo valor recibido como parametro
	 * 
	 * @param query
	 * @param value
	 * @return
	 * @throws ConsultaInadecuadaException
	 * @throws SQLException
	 * @throws Exception
	 */
	public static int inserta(StringBuffer query, String value)
			throws ConsultaInadecuadaException, SQLException, Exception {
		String[] param = { value };
		return inserta(query, param);
	}

	/**
	 * Realiza una insercion recibiendo una lista de parametros
	 * 
	 * @param query
	 * @param values
	 * @return
	 * @throws ConsultaInadecuadaException
	 * @throws SQLException
	 * @throws Exception
	 */
	/*
	 * public static ResultSet inserta(String query, List values) throws
	 * ConsultaInadecuadaException, SQLException, Exception { rs = null;
	 * String[] param = listaToArray(values); rs = inserta(query, param); return
	 * rs; }
	 */

	public static int inserta(StringBuffer query, List values)
			throws ConsultaInadecuadaException, SQLException, Exception {
		String[] param = listaToArray(values);
		return inserta(query, param);
	}

	/**
	 * 
	 * @param query
	 * @param value
	 * @throws ConsultaInadecuadaException
	 * @throws SQLException
	 * @throws Exception
	 */
	public static void borra(StringBuffer query, String value)
			throws ConsultaInadecuadaException, SQLException,
			NullPointerException, Exception {
		ps = null;
		if (value == null || value.length() == 0)
			throw new NullPointerException("El parametro es vacio");
		try {
			ps = connection.prepareStatement(query.toString());
			ps.setInt(1, Integer.parseInt(value));
			ps.execute();
		} catch (SQLException sqle) {
			throw new SQLException("Fallo en el borrado");
		} catch (Exception e) {
			throw new Exception(
					"Se produjo un error desconocido durante la operaci�n solicitada");
		}
	}

	/**
	 * 
	 * @param query
	 * @param values
	 * @throws ConsultaInadecuadaException
	 * @throws SQLException
	 * @throws Exception
	 */
	public static void borra(StringBuffer query, String[] values)
			throws ConsultaInadecuadaException, SQLException,
			NullPointerException, Exception {
		ps = null;
		if (values == null || values.length == 0)
			throw new NullPointerException("Los parametros son vacios");
		for (int i = 0; i < values.length; i++)
			borra(query, values[i]);
	}

	public static void actualiza(StringBuffer query) throws Exception {
		rs = null;
		try {
			statement = connection.createStatement();
			statement.execute(query.toString());
		} catch (SQLException sqle) {
			throw new SQLException("Fallo en la actualizacion: "
					+ sqle.toString());
		} catch (Exception e) {
			throw new Exception(
					"Se produjo un error desconocido durante la actualizacion: "
							+ e.toString());
		}
	}

	/**
	 * 
	 * @param query
	 * @param values
	 * @throws ConsultaInadecuadaException
	 * @throws SQLException
	 * @throws Exception
	 */
	public static void actualiza(StringBuffer query, String[] values)
			throws ConsultaInadecuadaException, SQLException,
			NullPointerException, Exception {
		rs = null;
		ps = null;
		if (values == null || values.length == 0)
			throw new NullPointerException("Los parametros son vacios");
		try {
			ps = connection.prepareStatement(query.toString());
			for (int i = 0; i < values.length; i++) {
				ps.setString(i + 1, values[i]);
			}
			ps.execute();
			System.out.println("Actualizado");
		} catch (SQLException sqle) {
			throw new SQLException("Fallo en la actualizacion: "
					+ sqle.toString());
		} catch (Exception e) {
			throw new Exception(
					"Se produjo un error desconocido durante la actualizacion: "
							+ e.toString());
		}
	}

	public static void actualiza(StringBuffer query, String value)
			throws ConsultaInadecuadaException, SQLException,
			NullPointerException, Exception {
		rs = null;
		ps = null;
		if (value == null || value.length() == 0)
			throw new NullPointerException("El parametro es vacio");
		try {
			ps = connection.prepareStatement(query.toString());
			ps.setString(1, value);
			ps.execute();
			System.out.println("Actualizado");
		} catch (SQLException sqle) {
			throw new SQLException("Fallo en la actualizacion: "
					+ sqle.toString());
		} catch (Exception e) {
			throw new Exception(
					"Se produjo un error desconocido durante la actualizacion: "
							+ e.toString());
		}
	}

	/**
	 * 
	 * @param query
	 * @param params
	 * @throws ConsultaInadecuadaException
	 * @throws SQLException
	 * @throws Exception
	 */
	public static void actualiza(StringBuffer query, List params)
			throws ConsultaInadecuadaException, SQLException,
			NullPointerException, Exception {
		String[] values = listaToArray(params);
		actualiza(query, values);

	}

	// protect data with quotes
	public static String quote(String include) {
		return ("\"" + include + "\"");
	}

	/**
	 * Verifica que la sentencia SQL no es de borrado (DROP, TRUNCATE) ni
	 * creaci�n (CREATE) de una tabla
	 * 
	 * @param queryStr
	 * @return
	 * @throws ConsultaInadecuadaException
	 */
	private static boolean sentenciaValida(StringBuffer query, String[] values,
			String tipoQuery) throws ConsultaInadecuadaException,
			NullPointerException {
		boolean b = true;
		String queryStr = query.toString();
		// se pone la consulta en mayusculas para evitar casuisticas
		queryStr = queryStr.toUpperCase();
		// comprobamos que la consulta no sea de borrado ni de creacion de tabla
		// Pregunta: Y si tenemos?
		// SELECT * FROM tabla ; ->Inyeccion: INSERT INTO tabla(.,.,.) VALUES
		// ...;
		// Mejora: Por cada sentencia SQL debe haber un ";" por lo que en un
		// conjunto de
		// dos o mas sentencias deberia existir mas de un ";"
		// Otro problema... y si existen valores en la tupla que contengan pto y
		// coma?
		// Hace falta usar expresiones regulares!!
		Pattern p = Pattern.compile(";");

		if ((queryStr.contains("DROP") || queryStr.contains("CREATE") || queryStr
				.contains("TRUNCATE"))) {
			b = false;
			throw new ConsultaInadecuadaException("Sentencia incorrecta");
		}
		// comprobamos que la consulta sea del tipo correcto
		if (!queryStr.contains(tipoQuery)) {
			b = false;
			throw new ConsultaInadecuadaException("La sentencia SQL:"
					+ quote(queryStr) + " no es de tipo " + tipoQuery);
		}
		// comprobamos que el array de parametros recibidos no es vacio ni nulo
		// (solo para sentecias distintas de SELECT)
		if (!tipoQuery.equals("SELECT")) {
			if (values == null || values.length == 0) {
				b = false;
				throw new NullPointerException("Los parametros son vacios");
			}
		}
		return b;
	}

	private static String[] listaToArray(List lista)
			throws NullPointerException {
		if (lista == null || lista.isEmpty()) {
			throw new NullPointerException("La lista de parametros es nula");
		}
		String[] arrayStr = new String[lista.size()];
		lista.toArray(arrayStr);
		return arrayStr;
	}

	/**
	 * Este metodo se usa con el fin de testear los metodos de GestorBD mas
	 * rapidamente
	 * 
	 * @param args
	 * @throws ConsultaInadecuadaException
	 * @throws SQLException
	 * @throws Exception
	 */
	public static void main(String args[]) throws ConsultaInadecuadaException,
			SQLException, Exception {
		// String queryIns = "UPDATE url_enlace SET url_enlace = ?,
		// descripcion_url = ?, id_enlace = ? ";
		// queryIns += "WHERE id_url_enlace = 43";
		String id_lugar = "5";
		StringBuffer query = new StringBuffer(
				"UPDATE elemento_mapa em SET em.nombre = ?, em.descripcion = ?, em.id_tipo_elem_mapa = ?");
		query.append(" WHERE em.id_elem_mapa = " + id_lugar);
		// String params[] = { "Montellano", "Un pueblo mu bonito", "2" };
		List params = new LinkedList();
		params.add("Montellano");
		params.add("Un pueblo mu feoo");
		params.add("3");
		conectar();
		actualiza(query, params);
		desconectar();
	}
}
